﻿// Shader-Based-Image-Processing (SBIP)
// http://code.google.com/p/sbip/
//
// Copyright © Frank Nagl, 2009-2011
// admin@franknagl.de
//
namespace SBIP.Filter.NonSBIP
{
    using System;
    using System.Drawing;
    using System.Drawing.Imaging;
    using System.Windows.Forms;
    using Helper;
    using SBIP.Controls;
    using SBIP.Helper.ColorSpace;
    using cp = SBIP.Helper.ColorPalette;
using System.Collections.Generic;

    public class NSLuvColorSegmentation : BaseNonSBIPFilter
    {        
        private BitmapData dstBitmapData;
        private int h;
        // source luv color 
        private LUV luv1;
        private bool[,] processedPixels;
        private int processedPixelsCounter;
        // Pixel size
        private int ps; 
        // source rgb color 
        private RGB rgb1;       
        private BitmapData srcBitmapData;
        private int stride;
        private int w;

        private ProgressBarForm tmpForm;
        private readonly List<Point> tmpRecursivePixels;
        private List<Point> tmpNewSegmentPixels;
        private List<Point> tmpNotProcessedPixels;

        #region properties

        private byte Diameter { get { return ((byte)(TraceRadius * 2 + 1)); } }

        /// <summary> The number of detected segments. </summary>
        public int NumberOfSegments { get; protected set; }

        /// <summary> Determiens, whether the progress is shown in a window.
        /// </summary>
        public bool ShowProgressBar { get; set; }

        /// <summary> Segmentation tolerance for allowed euclidian distance of 
        /// similar LUV colors. Default: 2.5.</summary>
        public float ColorDistance { get; set; }

        /// <summary> Minimum number of pixels per segment. </summary>
        public int MinNumberOfSegmentPixels { get; set; }

        /// <summary>
        /// The radius of the segmentation filter (in pixels).
        /// </summary>
        public byte TraceRadius { get; set; }

        #endregion properties

        /// <summary>
        /// Initializes a new instance of the <see cref="NSSimpleColorSegmentation"/> class.
        /// </summary>
        public NSLuvColorSegmentation()
        {
            SupportedSrcPixelFormats = PixelFormatFlags.Color;
            ColorDistance = 2.5f;
            TraceRadius = 1;
            MinNumberOfSegmentPixels = 20;
            ShowProgressBar = true;
            tmpRecursivePixels = new List<Point>();
            tmpNewSegmentPixels = new List<Point>();
            tmpNotProcessedPixels = new List<Point>();
        }

        /// <summary>
        /// Processes the image.
        /// </summary>
        /// <param name="srcData">The source bitmap data.</param>
        /// <param name="dstData">The destination bitmap data.</param>
        protected override void Process(BitmapData srcData, BitmapData dstData)
        {
            w = srcData.Width;
            h = srcData.Height;
            int colorId = 0;
            // processed pixels flag array
            processedPixels = new bool[w, h];
            processedPixelsCounter = 0;
            srcBitmapData = srcData;
            dstBitmapData = dstData;
            NumberOfSegments = 0;
            tmpNotProcessedPixels.Clear();
            ps = Image.GetPixelFormatSize(srcBitmapData.PixelFormat) / 8;
            stride = srcBitmapData.Stride;
            // int offset = stride - srcBitmapData.Width * ps;

            
            if (ShowProgressBar)
            {
                tmpForm = new ProgressBarForm { 
                    ProgressBar = { Maximum = h }, 
                    StartPosition = FormStartPosition.CenterScreen };
                tmpForm.Show();
                // form.TopMost = true;                    
            }

            // segment the image
            unsafe
            {
                // for each line
                for (int y = 0; y < h; y++)
                {
                    RefreshFormProgressBar();
                    // for each pixel in line
                    for (int x = 0; x < w; x++)
                    {
                        // when pixel already processed, continue with next
                        if (processedPixels[x, y])
                        {
                            continue;
                        }

                        tmpNewSegmentPixels.Clear();
                        tmpRecursivePixels.Clear();
                        Point startCoord = new Point(x, y);
                        tmpNewSegmentPixels.Add(startCoord);

                        byte* src = (byte*)srcBitmapData.Scan0.ToPointer();
                        // align pointers
                        src += stride * startCoord.Y + startCoord.X * ps;
                        // get source color
                        rgb1 = new RGB(src[RGBA.R], src[RGBA.G], src[RGBA.B]);
                        luv1 = rgb1.To<LUV>();
                        Color color = cp.Colors[colorId];
                        bool foundNeighbourSegment =
                          CheckForMostSimilarNeighbourPixelsSegmentColor(
                          startCoord, ref color, ColorDistance);

                        processedPixels[x, y] = true;
                        processedPixelsCounter++;
                        DrawCoordPixel(startCoord, color);
                        GetSegment(startCoord, color);

                        while (tmpRecursivePixels.Count != 0)
                        {
                            Point np = tmpRecursivePixels[0];
                            tmpRecursivePixels.RemoveAt(0);
                            GetSegment(np, color);
                        }

                        // reset pixels of to small segments
                        if (tmpNewSegmentPixels.Count < MinNumberOfSegmentPixels)
                        {
                            tmpNotProcessedPixels.AddRange(tmpNewSegmentPixels);

                            foreach (Point coord in tmpNewSegmentPixels)
                            {
                                // set free for potential other segments
                                processedPixels[coord.X, coord.Y] = false;
                                processedPixelsCounter--;
                            }

                            continue;
                        }

                        if (!foundNeighbourSegment)
                        {
                            colorId++;
                            NumberOfSegments++;
                            if (colorId == cp.ColorCount)
                            {
                                colorId = 0;
                            }
                        }
                    }
                }

                foreach (Point coord in tmpNotProcessedPixels)
                {
                   // RefreshFormProgressBar();
                    if (processedPixels[coord.X, coord.Y])
                    {
                        continue;
                    }

                    Color c = Color.White;
                    byte tmpRadius = TraceRadius;
                    // find best matched neighbour
                    while (!CheckForMostSimilarNeighbourPixelsSegmentColor(
                        coord, ref c, float.MaxValue))
                    {
                        TraceRadius++;
                    }

                    TraceRadius = tmpRadius;
                    DrawCoordPixel(coord, c);
                }                
            }

            if (ShowProgressBar && tmpForm != null)
            {
                tmpForm.Dispose();
                MessageBox.Show(
                    NumberOfSegments + @" Segments.");
            }
        }

        protected unsafe bool CheckForMostSimilarNeighbourPixelsSegmentColor(
                   Point coord, ref Color color, float minColorDistance)
        {
            bool found = false;
            float newDistance = minColorDistance;
            byte* src = (byte*)srcBitmapData.Scan0.ToPointer();
            byte* dst = (byte*)dstBitmapData.Scan0.ToPointer();
            // align pointers
            src += stride * coord.Y + coord.X * ps;
            dst += stride * coord.Y + coord.X * ps;

            // for each kernel row
            for (int i = 0; i < Diameter; i++)
            {
                int ir = i - TraceRadius;
                int ty = coord.Y + ir;

                // skip row
                if (ty < 0)
                    continue;
                // break
                if (ty >= h)
                    break;

                // for each kernel column
                for (int j = 0; j < Diameter; j++)
                {
                    int jr = j - TraceRadius;
                    int tx = coord.X + jr;

                    // skip column
                    if (tx < 0)
                        continue;

                    if (tx >= w)
                    {
                        continue;
                    }

                    // do not process identical pixels
                    if (ir == 0 && jr == 0)
                    {
                        continue;
                    }

                    // look for already processed pixels
                    if (processedPixels[tx, ty])
                    {
                        int index = ir * stride + jr * ps;
                        // get color
                        RGB rgb2 = new RGB(
                          src[index + RGBA.R], src[index + RGBA.G], src[index + RGBA.B]);
                        LUV luv2 = rgb2.To<LUV>();

                        float e = luv1.Color.ColorDistance(luv2.Color);
                        //// only u and v channel
                        //float e = Vector.Distance(
                        //    luv1.Color.B, luv1.Color.C, luv2.Color.B, luv2.Color.C);

                        // when not similar LUV colors, then go on
                        if (e > newDistance)
                        {
                            // Console.WriteLine(@"continue1");
                            continue;                            
                        }
                        color = Color.FromArgb(
                          dst[index + RGBA.R], dst[index + RGBA.G], dst[index + RGBA.B]);
                        newDistance = e;
                        tmpNewSegmentPixels.Add(new Point(tx, ty));
                        found = true;
                    }
                }
            }

            return found;
        }

        protected unsafe void DrawCoordPixel(Point coord, Color color)
        {
            byte* dst = (byte*)dstBitmapData.Scan0.ToPointer();
            // align pointer
            dst += stride * coord.Y + coord.X * ps;
            dst[RGBA.R] = color.R;
            dst[RGBA.G] = color.G;
            dst[RGBA.B] = color.B;
            if (ps == 4)
            {
                dst[RGBA.A] = 255;
            }
        }

        protected unsafe void GetSegment(Point coord, Color color)
        {          
            byte* src = (byte*)srcBitmapData.Scan0.ToPointer();
            byte* dst = (byte*)dstBitmapData.Scan0.ToPointer();
            
            // align pointers
            src += stride * coord.Y + coord.X * ps;
            dst += stride * coord.Y + coord.X * ps;

            //// get source color
            //RGB rgb1 = new RGB(src[RGBA.R], src[RGBA.G], src[RGBA.B]);
            //LUV luv1 = rgb1.To<LUV>();

            // for each kernel row
            for (int i = 0; i < Diameter; i++)
            {
                int ir = i - TraceRadius;
                int ty = coord.Y + ir;

                // skip row
                if (ty < 0)
                    continue;
                // break
                if (ty >= h)
                    break;

                // for each kernel column
                for (int j = 0; j < Diameter; j++)
                {
                    int jr = j - TraceRadius;
                    int tx = coord.X + jr;

                    // skip column
                    if (tx < 0)
                        continue;

                    if (tx >= w)
                    {
                        continue;
                    }

                    // do not process identical pixels
                    if (ir == 0 && jr == 0)
                    {
                        continue;
                    }

                    // do not process already processed pixels
                    if (processedPixels[tx, ty])
                    {
                        continue;
                    }

                    int index = ir * stride + jr * ps;

                    // get color
                    RGB rgb2 = new RGB(
                      src[index + RGBA.R], src[index + RGBA.G], src[index + RGBA.B]);
                    LUV luv2 = rgb2.To<LUV>();

                    float e = luv1.Color.ColorDistance(luv2.Color);
                    //// only u and v channel
                    //float e = Vector.Distance(
                    //    luv1.Color.B, luv1.Color.C, luv2.Color.B, luv2.Color.C);

                    // when not similar LUV colors, then go on
                    if (e > ColorDistance)
                    {
                        continue;
                    }                    

                    dst[index + RGBA.R] = color.R;
                    dst[index + RGBA.G] = color.G;
                    dst[index + RGBA.B] = color.B;
                    if (ps == 4)
                    {
                        dst[index + RGBA.A] = 255;
                    }

                    tmpRecursivePixels.Add(new Point(tx, ty));
                    tmpNewSegmentPixels.Add(new Point(tx, ty));
                    processedPixels[tx, ty] = true;
                    processedPixelsCounter++;

                    //// recursive process
                    //GetSegment(new Point(tx, ty), color);
                }
            }
        }

        private void RefreshFormProgressBar()
        {
            if (ShowProgressBar && tmpForm != null)
            {
                tmpForm.Text = @"Progress: " +
                    (int)((float)processedPixelsCounter / (w * h) * 100) + @"%";
                tmpForm.Resultlabel.Text =
                    @"Pixels to process: " + (w * h - processedPixelsCounter) +
                    @";    " + @"Detected segments: " + NumberOfSegments;
                tmpForm.ProgressBar.PerformStep();
                tmpForm.Refresh();
            }
        }
    }
}
